﻿using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class ActionInterpreter<THandledEventType,TDispatchedEventType> {
	private Dictionary<THandledEventType,AbstractActionEventStaticData> eventStatics = new Dictionary<THandledEventType, AbstractActionEventStaticData>();
	private Dictionary<THandledEventType,List<AbstractAction<THandledEventType,TDispatchedEventType>>> actions = new Dictionary<THandledEventType,List<AbstractAction<THandledEventType,TDispatchedEventType>>>();
	private List<ActionEvent<TDispatchedEventType>> newEvents = new List<ActionEvent<TDispatchedEventType>>();

	public ActionInterpreter ()
	{

	}

	public void AddEventStatic (THandledEventType handledEventType , AbstractActionEventStaticData eventStatic)
	{
		if (eventStatics.ContainsKey(handledEventType))
		{
			Debug.LogError("Failed to add event static for event type '" + handledEventType
			               + "'. Static data has already been added for that event type.");
			return;
		}

		eventStatics.Add(handledEventType , eventStatic);
	}

	public void RemoveEventStatic (THandledEventType handledEventType)
	{
		RemoveEventType(handledEventType); //An event type cannot be handled if it has no static data. If it exists, it should be removed before the static data is removed.
		eventStatics.Remove(handledEventType);
	}

	public void AddAction (THandledEventType handledEventType , AbstractAction<THandledEventType,TDispatchedEventType> action)
	{
		if (!eventStatics.ContainsKey(handledEventType))
		{
			Debug.LogError("Cannot add an action for event type '" + handledEventType + "' without first adding event static data for that type.");
			return;
		}

		List<AbstractAction<THandledEventType,TDispatchedEventType>> eventActions = null;
		if (actions.ContainsKey(handledEventType))
		{
			eventActions = actions[handledEventType];
		}
		else
		{
			eventActions = new List<AbstractAction<THandledEventType,TDispatchedEventType>>();
			actions.Add(handledEventType , eventActions);
		}

		eventActions.Add(action);
	}
	
	public void RemoveEventType (THandledEventType handledEventType)
	{
		actions.Remove(handledEventType);
	}
	
	public int HandleEvents (List<ActionEvent<THandledEventType>> eventStates)
	{
		for (int i = 0 ; i < eventStates.Count ; i++)
		{
			ActionEvent<THandledEventType> eventState = eventStates[i];
			
			bool success = HandleEvent(eventState);
			
			if (!success)
			{
				return i;
			}
		}
		return -1;
	}
	
	public bool HandleEvent (ActionEvent<THandledEventType> eventState)
	{
		if (!eventState.test && !eventState.execute)
		{
			Debug.LogWarning("Both the 'test' and the 'execute' properties of the event with id '" + eventState.id + "' and type '" + eventState.type + "' are false. The event will skipped.");
			return true;
		}

		if (!eventStatics.ContainsKey(eventState.type))
		{
			Debug.LogError("No static data for event type '" + eventState.type + "'.");
			return false;
		}
		
		if (!actions.ContainsKey(eventState.type))
		{
			Debug.LogError("No actions for event type '" + eventState.type + "'.");
			return false;
		}
		
		AbstractActionEventStaticData eventStatic = eventStatics[eventState.type];
		List<AbstractAction<THandledEventType,TDispatchedEventType>> eventActions = actions[eventState.type];
		
		bool success = true;
		success = InitActions(eventStatic , eventState , eventActions);

		if (success)
		{
			eventState.initialized = true;

			if (eventState.test)
			{
				success = TestActions(eventState , eventActions);
				eventState.tested = true;
			}

			if (success)
			{
				if (eventState.execute)
				{
					eventState.executionStarted = true;
					success = ExecuteActions(eventState , eventActions);
					if (success)
					{
						eventState.executed = true;
						success = FinishActions(eventState , eventActions);
						eventState.finished = success;
					}
				}
			}
		}

		eventState.success = success;

		RecycleActions(eventActions);
		return success;
	}

	public List<ActionEvent<TDispatchedEventType>> NewEvents
	{
		get
		{
			return newEvents;
		}
	}

	public void ResetNewEvents ()
	{
		newEvents = new List<ActionEvent<TDispatchedEventType>>();
	}

	private bool InitActions (AbstractActionEventStaticData eventStatic , ActionEvent<THandledEventType> eventState , List<AbstractAction<THandledEventType,TDispatchedEventType>> eventActions)
	{
		Hashtable actionParams = new Hashtable();
		for (int i = 0 ; i < eventActions.Count ; i++)
		{
			AbstractAction<THandledEventType,TDispatchedEventType> action = eventActions[i];
			bool success = action.Init(eventStatic , eventState , actionParams);

			if (!success)
			{
				if (eventState.initThrowErrors)
				{
					Debug.LogError("Initiation of event with id '" + eventState.id + "' and type '" + eventState.type + "' failed in action at index '" + i + "' with name '" + action.name + "'.");
				}
				return false;
			}
		}
		return true;
	}
	
	private bool TestActions (ActionEvent<THandledEventType> eventState , List<AbstractAction<THandledEventType,TDispatchedEventType>> eventActions)
	{
		for (int i = 0 ; i < eventActions.Count ; i++)
		{
			AbstractAction<THandledEventType,TDispatchedEventType> action = eventActions[i];
			bool success = action.Test();

			if (!success)
			{
				if (eventState.testThrowErrors)
				{
					Debug.LogError("Test of event with id '" + eventState.id + "' and type '" + eventState.type + "' failed in action at index '" + i + "' with name '" + action.name + "'.");
				}
				return false;
			}
		}
		return true;
	}

	private bool ExecuteActions (ActionEvent<THandledEventType> eventState , List<AbstractAction<THandledEventType,TDispatchedEventType>> eventActions)
	{
		for (int i = 0 ; i < eventActions.Count ; i++)
		{
			AbstractAction<THandledEventType,TDispatchedEventType> action = eventActions[i];
			bool success = action.Execute(newEvents);

			if (!success)
			{
				if (eventState.executeThrowErrors)
				{
					Debug.LogError("Execution of event with id '" + eventState.id + "' and type '" + eventState.type + "' failed in action at index '" + i + "' with name '" + action.name + "'.");
				}

				return false;
			}
		}

		return true;
	}

	private bool FinishActions (ActionEvent<THandledEventType> eventState , List<AbstractAction<THandledEventType,TDispatchedEventType>> eventActions)
	{
		for (int i = 0 ; i < eventActions.Count ; i++)
		{
			AbstractAction<THandledEventType,TDispatchedEventType> action = eventActions[i];
			bool success = action.Finish(newEvents);
			
			if (!success)
			{
				if (eventState.finishThrowErrors)
				{
					Debug.LogError("Finishing of event with id '" + eventState.id + "' and type '" + eventState.type + "' failed in action at index '" + i + "' with name '" + action.name + "'.");
				}
				return false;
			}
		}
		return true;
	}
	
	private void RecycleActions (List<AbstractAction<THandledEventType,TDispatchedEventType>> eventActions)
	{
		for (int i = 0 ; i < eventActions.Count ; i++)
		{
			AbstractAction<THandledEventType,TDispatchedEventType> action = eventActions[i];
			action.Recycle();
		}
	}
}
